/*************************************************************************
 * lptdriver: LPT driver for Linux
 *
 * Copyright (C) 2010 Brandon Sachtleben
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Please refer to the README for more info.
 *
 * <bsacht@gmail.com>
************************************************************************/

/* Defines */
#define OPT_READ		1
#define OPT_WRITE		2
#define OPT_BASE		4
#define OPT_ENUM		8

#define BIOS_LPT_BASE		0x408

/* Includes */
#include <errno.h>
#include <unistd.h>
#include <sys/io.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>

int ioport_init(const uint32_t *base_addr)
{
	ioperm(*base_addr, 3, 1);

	if(errno == EINVAL)
	{
		printf("Error: Invalid base address.\n");

		return 0;
	}

	if(errno == EIO)
	{
		printf("Error: ioperm() is not supported "
			"on this system.\n");

		return 0;
	}

	if(errno == EPERM)
	{
		printf("Error: Insufficient privileges for call to "
			"ioperm(). You must have root access to run "
			"this program.\n");

		return 0;
	}

	return 1;
}

const uint8_t ioport_read(const uint32_t *base_addr)
{
	return inb(*base_addr);
}

void ioport_write(const uint32_t *base_addr, const uint32_t *data)
{
	/* Sets the data bits */
	outb(*data, *base_addr);
}

void ioport_reset()
{
}

/***************************************************************************
 * BIOS incrementally assigns addresses to the LPT ports starting at 0x408.
 * This function accesses those addresses from /dev/mem and displays a list.
 **************************************************************************/
int lpt_enum_addr()
{
	int fd;
	int off;
	uint16_t addr;

	/* Open the memory device */
	if((fd = open("/dev/mem", O_RDONLY)) == -1)
	{
		printf("Error: Cannot open /dev/mem for reading. "
			"Make sure you have root privileges.\n");
		return 0;
	}

	/* Seek to the base address */
	lseek(fd, BIOS_LPT_BASE, SEEK_SET);

	/* Read the given port address into addr */
	for(off = 0; off < 3; ++off)
	{
		read(fd, &addr, 2);

		if(addr == 0)
			printf("LPT%d: Not found.\n", off+1);
		else
			printf("LPT%d: Assigned to address 0x%X\n", off+1, addr);
	}
	
	close(fd);

	return 1;
}

int main(int argc, char **argv)
{
	if(argc == 1)
	{
		printf("Usage: lptdriver [OPTIONS]\n"
			"   -b  --base\t\t"
			"Indicates the base address of the parallel port.\n"
			"   -w  --write\t\t"
			"Data to write to the base address represented in hex.\n"
			"   -r  --read\t\t"
			"Flag option. Simply displays the data from the port.\n"
			"   -e  --enum\t\t"
			"Displays a lookup table from BIOS for the assigned LPTs.\n\n"
			"Refer to README for more info.\n");

		return 1;
	}

	uint32_t iobase_addr;
	uint32_t data;
	uint8_t opt_flags = 0;

	int i;
	for(i = 1; i < argc; i++)
	{
		/* Request access for data, status, and control ports */
		if(!strcmp(argv[i], "--base") || !strcmp(argv[i], "-b"))
		{
			if((i+1) > (argc-1))
			{
				printf("Error: Please supply a parameter.\n");

				return EXIT_FAILURE;
			}

			sscanf(argv[i+1], "%x", &iobase_addr);

			/* Get port permissions */
			if(!ioport_init(&iobase_addr))
				return EXIT_FAILURE;
			
			opt_flags |= OPT_BASE;
		}

		/* Store the write bits */
		if(!strcmp(argv[i], "--write") || !strcmp(argv[i], "-w"))
		{
			if((i+1) > (argc-1))
			{
				printf("Error: Please supply a parameter.\n");

				return EXIT_FAILURE;
			}

			sscanf(argv[i+1], "%x", &data);

			opt_flags |= OPT_WRITE;
		}

		/* Flag for read operation */
		if(!strcmp(argv[i], "--read") || !strcmp(argv[i], "-r"))
		{
			opt_flags |= OPT_READ;
		}

		/* Enumerate LPT ports from BIOS info */
		if(!strcmp(argv[i], "--enum") || !strcmp(argv[i], "-e"))
		{
			if(!lpt_enum_addr())
			{
				printf("Error: enum has failed to find LPT addresses. "
					"Try 'cat /proc/ioports'.\n");

				return EXIT_FAILURE;
			}

			opt_flags |= OPT_ENUM;
		}
	}

	if(!(opt_flags & OPT_ENUM))
	{
		if(!(opt_flags & OPT_BASE))
		{
			printf("Error: Please provide a base address in the arguments.\n");

			return EXIT_FAILURE;
		}

		if(!(opt_flags & OPT_WRITE) && !(opt_flags & OPT_READ))
		{
			printf("Error: Please specify a read/write operation "
				"in the arguments.\n");

			return EXIT_FAILURE;
		}
	}

	/* Write the data to the port */
	if(opt_flags & OPT_WRITE)
		ioport_write(&iobase_addr, &data);

	/* Read data from the port */
	if(opt_flags & OPT_READ)
		printf("Port 0x%X returns value: 0x%X\n", iobase_addr,
			ioport_read(&iobase_addr));

	return EXIT_SUCCESS;
}

